﻿using CefGlue.Avalonia.Http;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using CefV8Handler = Xilium.CefGlue.CefV8Handler;
using CefV8Value = Xilium.CefGlue.CefV8Value;

namespace CefGlue.Avalonia.Handlers
{
    public class ObjectTypeInfo
    {
        public object Object { get; set; }
        public Type Type { get; set; }
    }
    public class CefV8HandlerEx
    {
        public static async Task<(bool IsSuccess, Object ReturnValue, Type ReturnType)> CallMethod(MethodInfo method, JavascriptCallObject javascriptCallObject, CefV8Value[] arguments)
        {
            Boolean isHandle = false;
            var methodParams = method.GetParameters();
            object[] objectParamList = new object[methodParams.Length];


            for (int i = 0; i < methodParams.Length; i++)
            {
                if (arguments != null && i < arguments.Length)
                {
                    CefV8Value arg = arguments[i];
                    object objarg = GetObjArg(arg, methodParams[i]);
                    objectParamList[i] = objarg;
                }
                else
                {
                    objectParamList[i] = methodParams[i].DefaultValue;
                }
            }
            Dictionary<int, object> paramDict = new Dictionary<int, object>();
            for (int i = 0; i < objectParamList.Length; i++)
            {
                paramDict.Add(i, objectParamList[i]);

            }
            string requestUrl = $"http://127.0.0.1:{CommonConst.HttpPort}{CommonConst.WebapiPath}";
            var requestObject = new JavascriptCallObject
            {
                ObjectId = javascriptCallObject.ObjectId,
                ObjectType = javascriptCallObject.ObjectType,
                MethodName = javascriptCallObject.MethodName,
                AssemblyName = javascriptCallObject.AssemblyName,
                CallParams = paramDict
            };
            string json = Newtonsoft.Json.JsonConvert.SerializeObject(requestObject);
            using HttpClient httpClient = new HttpClient();
            StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
            using var response = await httpClient.PostAsync(requestUrl, content);
            string returnJson = await response.Content.ReadAsStringAsync();

            var jsReturnObject = Newtonsoft.Json.Linq.JObject.Parse(returnJson);
            object returnObject = null;
            Type returnType = method.ReturnParameter.ParameterType;
            if (returnType == typeof(void))
            {
                return  (isHandle, returnObject, returnType);
            }
            if (method.ReturnParameter.ParameterType.BaseType == typeof(Task))
            {
                returnType = returnType.GenericTypeArguments.FirstOrDefault();
            }
#pragma warning disable CS0168 // 声明了变量，但从未使用过
            try
            {  
                returnObject = Convert.ChangeType(jsReturnObject["Object"]?.ToString(), returnType);
            }
            catch (Exception ex)
            { 
                returnObject = Newtonsoft.Json.JsonConvert.DeserializeObject(jsReturnObject["Object"]?.ToString(), returnType);
            }
#pragma warning restore CS0168 // 声明了变量，但从未使用过
            //CefV8Value returnValue = ReturnObjToCefV8Value(returnObject,returnType);
            //isHandle = true;
            return (isHandle, returnObject, returnType);
        }


        internal static CefV8Value ReturnObjToCefV8Value(object returnObj, Type type)
        {
            CefV8Value cefReturnValue = CefV8Value.CreateNull();
            if (returnObj == null)
            {
                return cefReturnValue;
            }
            if (type == typeof(void) || type == typeof(Task))
            {
                return cefReturnValue;
            }

            if (type.BaseType == typeof(Task))
            {
                object realReturnValue = type.GetProperty("Result").GetValue(returnObj);
                cefReturnValue = ReturnObjToCefV8Value(realReturnValue, type.GenericTypeArguments.FirstOrDefault());
                return cefReturnValue;
            }
            if (IsTypeInt(type))
            {
                cefReturnValue = CefV8Value.CreateInt(Convert.ToInt32(returnObj));
            }
            else if (IsTypeUInt(type))
            {
                cefReturnValue = CefV8Value.CreateUInt(Convert.ToUInt32(returnObj));
            }
            else if (IsTypeDate(type))
            {
                DateTime time = Convert.ToDateTime(returnObj);
                //CefBaseTime cefBaseTime = DatetimeToCefTime(time);
                cefReturnValue = CefV8Value.CreateDate(time);
            }
            else if (type == typeof(string))
            {
                cefReturnValue = CefV8Value.CreateString(Convert.ToString(returnObj));
            }
            else if (type == typeof(Boolean))
            {
                cefReturnValue = CefV8Value.CreateBool(Convert.ToBoolean(returnObj));
            }
            else if (IsTypeDouble(type))
            {
                cefReturnValue = CefV8Value.CreateDouble(Convert.ToDouble(returnObj));
            }
            else if (IsDictionary(type))
            {
                var map = returnObj as IDictionary;
                cefReturnValue = CefV8Value.CreateObject();
                foreach (var prop in map.Keys)
                {
                    object propValue = map[prop];
                    CefV8Value cefPropValue = ReturnObjToCefV8Value(propValue, propValue != null ? propValue.GetType() : typeof(void));
                    cefReturnValue.SetValue(Convert.ToString(prop), cefPropValue);
                }
            }
            else if (type.IsArray || type.IsGenericType)
            {
                Type elementType = type.IsGenericType ? type.GenericTypeArguments.FirstOrDefault() : type.GetElementType();
                IEnumerable<object> dataList = returnObj as IEnumerable<object>;
                cefReturnValue = CefV8Value.CreateArray(dataList.Count());
                if (IsBasicType(elementType))
                {
                    int i = 0;
                    foreach (var obj in dataList)
                    {
                        CefV8Value basicValue = ReturnObjToCefV8Value(obj, elementType);
                        cefReturnValue.SetValue(i++, basicValue);
                    }
                }
                else
                {
                    PropertyInfo[] objectProperties = elementType.GetProperties();

                    int i = 0;
                    foreach (var obj in dataList)
                    {
                        CefV8Value cefObject = CefV8Value.CreateObject();
                        foreach (var prop in objectProperties)
                        {
                            object propValue = prop.GetValue(obj);
                            CefV8Value cefPropValue = ReturnObjToCefV8Value(propValue, prop.PropertyType);
                            cefObject.SetValue(prop.Name, cefPropValue);
                        }
                        cefReturnValue.SetValue(i++, cefObject);
                    }
                }
            }
            else //具体的对象了
            {
                PropertyInfo[] objectProperties = type.GetProperties();
                cefReturnValue = CefV8Value.CreateObject();

                foreach (var prop in objectProperties)
                {
                    object propValue = prop.GetValue(returnObj);
                    CefV8Value cefPropValue = ReturnObjToCefV8Value(propValue, prop.PropertyType);
                    cefReturnValue.SetValue(prop.Name, cefPropValue);
                }
            }
            return cefReturnValue;
        }

        //private static CefBaseTime DatetimeToCefTime(DateTime time)
        //{
        //    CefTime ceftime = new CefTime(time);
        //    CefBaseTime cefBaseTime = new CefBaseTime();
        //    CefBaseTime.FromUtcExploded(ceftime, out cefBaseTime);
        //    return cefBaseTime;
        //}

        //private static DateTime CefTimeToDateTime(CefBaseTime time)
        //{
        //    CefTime ceftime = new CefTime();
        //    time.UtcExplode(out ceftime);
        //    return new DateTime(ceftime.Year, ceftime.Month, ceftime.DayOfWeek, ceftime.Hour, ceftime.Minute, ceftime.Second, ceftime.Millisecond);
        //}
        private static bool IsDictionary(Type type)
        {
            bool isOk = false;
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            {
                isOk = true;
            }
            return isOk;
        }
        private static bool IsBasicType(Type type)
        {
            if (IsTypeDate(type)
                || IsTypeDouble(type)
                || IsTypeInt(type)
                || IsTypeUInt(type)
                || typeof(string) == type
                || typeof(Boolean) == type)
            {
                return true;
            }
            return false;
        }
        private static bool IsTypeDouble(Type type)
        {
            bool isdouble = false;
            if (type == typeof(float)
                || type == typeof(Double)
                || type == typeof(Decimal))
            {
                isdouble = true;
            }
            return isdouble;
        }
        private static bool IsTypeInt(Type type)
        {
            bool isint = false;
            if (type == typeof(int)
                || type == typeof(SByte)
                || type == typeof(short)
                || type == typeof(long))
            {
                isint = true;
            }
            return isint;
        }

        private static bool IsTypeUInt(Type type)
        {
            bool isuint = false;
            if (type == typeof(UInt32)
                || type == typeof(Byte)
                || type == typeof(UInt16)
                || type == typeof(UInt64))
            {
                isuint = true;
            }
            return isuint;
        }

        private static bool IsTypeDate(Type type)
        {
            bool isdate = false;
            if (type == typeof(DateTime)
                || type == typeof(DateTimeOffset)
                || type == typeof(TimeSpan)
                )
            {
                isdate = true;
            }
            return isdate;
        }
        private static Object CreateList(Type type)
        {
            Type listType = typeof(List<>).MakeGenericType(type);
            Object listObject = Activator.CreateInstance(listType, new object[] { });
            return listObject;
        }
        private static dynamic GetObjArg(CefV8Value arg, ParameterInfo parameterInfo)
        {
            dynamic returnObj = null;
            if (arg.IsArray)
            {
                Boolean isRealArray = true;
                int elementLength = arg.GetArrayLength();
                Type elementType = parameterInfo.ParameterType.GetElementType();
                if (elementType == null)
                {
                    elementType = parameterInfo.ParameterType.GenericTypeArguments.FirstOrDefault();
                    isRealArray = false;
                }
                Type listType = typeof(List<>).MakeGenericType(elementType);
                var paramList = Activator.CreateInstance(listType) as IList;
                for (int i = 0; i < elementLength; i++)
                {
                    CefV8Value value = arg.GetValue(i);
                    dynamic listValueObj = CefV8ValueToObject(value, elementType);
                    paramList.Add(listValueObj);
                }
                if (!isRealArray)
                {
                    returnObj = paramList;
                }
                else
                {
                    var array = Array.CreateInstance(elementType, elementLength);
                    for (int i = 0; i < elementLength; i++)
                    {
                        array.SetValue(paramList[i], i);
                    }
                    return array;
                }

            }
            else
            {
                returnObj = CefV8ValueToObject(arg, parameterInfo.ParameterType);
            }
            return returnObj;
        }
        private static dynamic CefV8ValueToObject(CefV8Value cefV8Value, Type type)
        {
            dynamic destObj = null;
            if (cefV8Value.IsBool)
            {
                destObj = cefV8Value.GetBoolValue();
            }
            else if (cefV8Value.IsInt)
            {
                destObj = cefV8Value.GetIntValue();
            }
            else if (cefV8Value.IsUInt)
            {
                destObj = cefV8Value.GetUIntValue();
            }
            else if (cefV8Value.IsDate)
            {
                var date = cefV8Value.GetDateValue();
                DateTime dateTime = date;
                destObj = dateTime;
            }
            else if (cefV8Value.IsString)
            {
                destObj = cefV8Value.GetStringValue();
            }
            else if (cefV8Value.IsArray)
            {
                List<dynamic> paramList = new List<dynamic>();
                Type elementType = type.GenericTypeArguments.FirstOrDefault();
                if (elementType == null)
                {
                    elementType = type.GetElementType();
                }
                for (int i = 0; i < cefV8Value.GetArrayLength(); i++)
                {
                    CefV8Value value = cefV8Value.GetValue(i);

                    dynamic listValueObj = CefV8ValueToObject(value, elementType);
                    paramList.Add(listValueObj);
                }
                destObj = paramList;
            }
            else if (cefV8Value.IsObject)
            {
                var keys = cefV8Value.GetKeys();
                PropertyInfo[] properties = type.GetProperties();
                Assembly assembly = type.Assembly;
                destObj = assembly.CreateInstance(type.FullName);
                foreach (var key in keys)
                {
                    var value = cefV8Value.GetValue(key);
                    var prop = properties.FirstOrDefault(t => t.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase));
                    var propObj = CefV8ValueToObject(value, prop.PropertyType);
                    prop.SetValue(destObj, Convert.ChangeType(propObj, prop.PropertyType));
                }
            }
            return destObj;
        }

    }
    public class JavascriptCefV8Handler : CefV8Handler
    {
        static ConcurrentDictionary<int, ObjectTypeInfo> CallObjMap = new ConcurrentDictionary<int, ObjectTypeInfo>();
        static ConcurrentDictionary<string, MethodInfo> CallMethodMap = new ConcurrentDictionary<string, MethodInfo>();

        public JavascriptCefV8Handler()
        {

        }
        protected override bool Execute(string name, CefV8Value obj, CefV8Value[] arguments, out CefV8Value returnValue, out string exception)
        {
            returnValue = CefV8Value.CreateNull();
            exception = null;
            int objId = obj.GetValue("Id").GetIntValue();
            string objectName = obj.GetValue("Name").GetStringValue();
            string methodName = name;
            string ojectTypeName = obj.GetValue("ObjectTypeName").GetStringValue();
            string assemblyName = obj.GetValue("AssemblyName").GetStringValue();
            ObjectTypeInfo callObject = null;
            Type callObjType = null;
            if (!CallObjMap.TryGetValue(objId, out callObject))
            {
                Assembly assembly = Assembly.Load(assemblyName);
                callObjType = assembly.GetType(ojectTypeName, true, true);
                // object Object = Activator.CreateInstance(callObjType);
                callObject = new ObjectTypeInfo() { Object = null, Type = callObjType };
                CallObjMap.AddOrUpdate(objId, (id) => { return callObject; }, (id, o) => { return callObject; });
            }
            if (callObject == null)
            {
                exception = $"object [{objectName}] does not exists";
                return true;
            }
            callObjType = callObject.Type;
            string methodKey = $"{objId}-{methodName}";
            if (!CallMethodMap.TryGetValue(methodKey, out MethodInfo callMethod))
            {
                callMethod = callObjType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                CallMethodMap.AddOrUpdate(methodKey, (id) => { return callMethod; }, (id, o) => { return callMethod; });
            }
            if (callMethod == null)
            {
                exception = $"function [{name}] does not exists";
                return true;
            }
            bool isHandle = false;
            try
            {
                var requestParam = new JavascriptCallObject
                {
                    ObjectId = objId,
                    ObjectType = ojectTypeName,
                    AssemblyName = assemblyName,
                    MethodName = methodName,
                };
                var returnResult = CefV8HandlerEx.CallMethod(callMethod, requestParam, arguments).Result;
                returnValue = CefV8HandlerEx.ReturnObjToCefV8Value(returnResult.ReturnValue, returnResult.ReturnType);
                //isHandle = true;
                isHandle = true;

            }
            catch (Exception ex)
            {
                exception = $"Error:{ex.Message},Stack={ex.StackTrace}";
                isHandle = true;
            }
            return isHandle;
        }
    }
}
